---
title: Construye formularios con rutas API
description: Aprende como usar JavaScript para enviar formularios a una ruta API.
i18nReady: true
type: recipe
---
import UIFrameworkTabs from "~/components/tabs/UIFrameworkTabs.astro";
import PackageManagerTabs from "~/components/tabs/PackageManagerTabs.astro"

Un formulario HTML hace que el navegador se actualice o navegue a una nueva página. Para enviar datos de formulario a un endpoint de API en su lugar, debes interceptar el envío del formulario usando JavaScript.

Esta receta muestra como enviar datos de un formulario a un endpoint de API y manejar esos datos.

## Prerrequisitos
- Un proyecto con [SSR](/es/guides/server-side-rendering/) (`output: 'server'` o `'hybrid'`) habilitado
- Una [integración de Framework UI](/es/guides/framework-components/) instalada

## Receta

1. Crea un endpoint de API `POST` en `/api/feedback` que recibirá los datos del formulario. Usa `request.formData()` para procesarlos. Asegúrate de validar los valores del formularios antes de utilizarlos.

  Este ejemplo manda un objeto JSON con un mensaje de vuelta al cliente.

  ```ts title="src/pages/api/feedback.ts" "request.formData()" "post"
      import type { APIRoute } from "astro";

      export const POST: APIRoute = async ({ request }) => {
        const data = await request.formData();
        const name = data.get("name");
        const email = data.get("email");
        const message = data.get("message");
        // Valida los datos - probablemente querrás hacer más que esto
        if (!name || !email || !message) {
          return new Response(
            JSON.stringify({
              message: "Faltan campos requeridos",
            }),
            { status: 400 }
          );
        }
        // Haz algo con los datos, luego devuelve una respuesta de éxito
        return new Response(
          JSON.stringify({
            message: "¡Éxito!"
          }),
          { status: 200 }
        );
      };
      ```

  2. Crea un componente de formulario usando tu framework UI. Cada entrada debe tener un atributo `name` que describa el valor de esa entrada.
  
  Asegúrate de incluir un elemento `<button>` o `<input type="submit">` para enviar el formulario.

    <UIFrameworkTabs>
      <Fragment slot="preact">
        ```tsx title="src/components/FeedbackForm.tsx"
        export default function Form() {
          return (
            <form>
              <label>
                Nombre
                <input type="text" id="name" name="name" required />
              </label>
              <label>
                Correo electrónico
                <input type="email" id="email" name="email" required />
              </label>
              <label>
                Mensaje
                <textarea id="message" name="message" required />
              </label>
              <button>Enviar</button>
            </form>
          );
        }
        ```
      </Fragment>
      <Fragment slot="react">
        ```tsx title="src/components/FeedbackForm.tsx"
        export default function Form() {
          return (
            <form>
              <label>
                Nombre
                <input type="text" id="name" name="name" required />
              </label>
              <label>
                Correo electrónico
                <input type="email" id="email" name="email" required />
              </label>
              <label>
                Mensaje
                <textarea id="message" name="message" required />
              </label>
              <button>Enviar</button>
            </form>
          );
        }
        ```
      </Fragment>
      <Fragment slot="solid">
        ```tsx title="src/components/FeedbackForm.tsx"
        export default function Form() {
          return (
            <form>
              <label>
                Nombre
                <input type="text" id="name" name="name" required />
              </label>
              <label>
                Correo electrónico
                <input type="email" id="email" name="email" required />
              </label>
              <label>
                Mensaje
                <textarea id="message" name="message" required />
              </label>
              <button>Enviar</button>
            </form>
          );
        }
        ```
      </Fragment>
      <Fragment slot="svelte">
        ```svelte title="src/components/FeedbackForm.svelte"
        <form>
          <label>
            Nombre
            <input type="text" id="name" name="name" required />
          </label>
          <label>
            Correo electrónico
            <input type="email" id="email" name="email" required />
          </label>
          <label>
            Mensaje
            <textarea id="message" name="message" required />
          </label>
          <button>Enviar</button>
        </form>
        ```
      </Fragment>
      <Fragment slot="vue">
        ```vue title="src/components/FeedbackForm.vue"
        <template>
          <form>
            <label>
              Nombre
              <input type="text" id="name" name="name" required />
            </label>
            <label>
              Correo electrónico
              <input type="email" id="email" name="email" required />
            </label>
            <label>
              Mensaje
              <textarea id="message" name="message" required />
            </label>
            <button>Enviar</button>
          </form>
        </template>
        ```
      </Fragment>

    </UIFrameworkTabs>

  3. Crea una función que acepte un evento de envío, luego pásala como un controlador `submit` a tu formulario.

  En la función:
	- Llama a `preventDefault()` en el evento para anular el proceso de envío predeterminado del navegador.
	- Crea un objeto `FormData` y envíalo a tu endpoint como una solicitud `POST` usando `fetch()`.

  <UIFrameworkTabs>
    <Fragment slot="preact">
      ```tsx title="src/components/FeedbackForm.tsx" "/api/feedback" add={1, 4-17, 34} add="onSubmit={submit}" "formData" "e.preventDefault();"
      import { useState } from "preact/hooks";

      export default function Form() {
        const [responseMessage, setResponseMessage] = useState("");

        async function submit(e: SubmitEvent) {
          e.preventDefault();
          const formData = new FormData(e.target as HTMLFormElement);
          const response = await fetch("/api/feedback", {
            method: "POST",
            body: formData,
          });
          const data = await response.json();
          if (data.message) {
            setResponseMessage(data.message);
          }
        }

        return (
          <form onSubmit={submit}>
            <label>
              Nombre
              <input type="text" id="name" name="name" required />
            </label>
            <label>
              Correo electrónico
              <input type="email" id="email" name="email" required />
            </label>
            <label>
              Mensaje
              <textarea id="message" name="message" required />
            </label>
            <button>Enviar</button>
            {responseMessage && <p>{responseMessage}</p>}
          </form>
        );
      }

      ```
    </Fragment>
    <Fragment slot="react">
      ```tsx title="src/components/FeedbackForm.tsx" "/api/feedback" add={1, 4-17, 34} add="onSubmit={submit}" "formData" "e.preventDefault();"
      import { FormEvent, useState } from "react";

      export default function Form() {
        const [responseMessage, setResponseMessage] = useState("");

        async function submit(e: FormEvent<HTMLFormElement>) {
          e.preventDefault();
          const formData = new FormData(e.target as HTMLFormElement);
          const response = await fetch("/api/feedback", {
            method: "POST",
            body: formData,
          });
          const data = await response.json();
          if (data.message) {
            setResponseMessage(data.message);
          }
        }

        return (
          <form onSubmit={submit}>
            <label htmlFor="name">
              Nombre
              <input type="text" id="name" name="name" autoComplete="name" required />
            </label>
            <label htmlFor="email">
              Correo electrónico
              <input type="email" id="email" name="email" autoComplete="email" required />
            </label>
            <label htmlFor="message">
              Mensaje
              <textarea id="message" name="message" autoComplete="off" required />
            </label>
            <button>Enviar</button>
            {responseMessage && <p>{responseMessage}</p>}
          </form>
        );
      }
      ```
    </Fragment>
    <Fragment slot="solid">
      ```tsx title="src/components/FeedbackForm.tsx" "/api/feedback" add={1, 3-9, 13-19, 36} add="onSubmit={submit}" "formData" "e.preventDefault();"
      import { createSignal, createResource, Suspense } from "solid-js";

      async function postFormData(formData: FormData) {
        const response = await fetch("/api/feedback", {
          method: "POST",
          body: formData,
        });
        const data = await response.json();
        return data;
      }

      export default function Form() {
        const [formData, setFormData] = createSignal<FormData>();
        const [response] = createResource(formData, postFormData);

        function submit(e: SubmitEvent) {
          e.preventDefault();
          setFormData(new FormData(e.target as HTMLFormElement));
        }

        return (
          <form onSubmit={submit}>
            <label>
              Nombre
              <input type="text" id="name" name="name" required />
            </label>
            <label>
              Correo electrónico
              <input type="email" id="email" name="email" required />
            </label>
            <label>
              Mensaje
              <textarea id="message" name="message" required />
            </label>
            <button>Enviar</button>
            <Suspense>{response() && <p>{response().message}</p>}</Suspense>
          </form>
        );
      }

      ```
    </Fragment>
    <Fragment slot="svelte">
      ```svelte title="src/components/FeedbackForm.svelte" "/api/feedback" add={1-14, 30-32} add="on:submit={submit}" "formData" "e.preventDefault();"
      <script lang="ts">
        let responseMessage: string;

        async function submit(e: SubmitEvent) {
          e.preventDefault();
          const formData = new FormData(e.currentTarget as HTMLFormElement);
          const response = await fetch("/api/feedback", {
            method: "POST",
            body: formData,
          });
          const data = await response.json();
          responseMessage = data.message;
        }
      </script>

      <form on:submit={submit}>
        <label>
          Nombre
          <input type="text" id="name" name="name" required />
        </label>
        <label>
          Correo electrónico
          <input type="email" id="email" name="email" required />
        </label>
        <label>
          Mensaje
          <textarea id="message" name="message" required />
        </label>
        <button>Enviar</button>
        {#if responseMessage}
          <p>{responseMessage}</p>
        {/if}
      </form>
          ```
    </Fragment>
    <Fragment slot="vue">
      ```vue title="src/components/FeedbackForm.vue" "/api/feedback" add={1-16, 33} "formData" "e.preventDefault();"
      <script setup lang="ts">
      import { ref } from "vue";

      const responseMessage = ref<string>();

      async function submit(e: Event) {
        e.preventDefault();
        const formData = new FormData(e.currentTarget as HTMLFormElement);
        const response = await fetch("/api/feedback", {
          method: "POST",
          body: formData,
        });
        const data = await response.json();
        responseMessage.value = data.message;
      }
      </script>

      <template>
        <form @submit="submit">
          <label>
            Nombre
            <input type="text" id="name" name="name" required />
          </label>
          <label>
            Correo electrónico
            <input type="email" id="email" name="email" required />
          </label>
          <label>
            Mensaje
            <textarea id="message" name="message" required />
          </label>
          <button>Enviar</button>
          <p v-if="responseMessage">{{ responseMessage }}</p>
        </form>
      </template>
      ```
    </Fragment>

  </UIFrameworkTabs>

  4. Importa y usa el componente `<FeedbackForm />` en la página. Asegúrate de utilizar una directiva `client:*` para garantizar que la lógica del formulario se hidrate cuando lo deseas.
    
  <UIFrameworkTabs>
      <Fragment slot="preact">
      ```astro title="src/pages/index.astro" "client:load"
      ---
      import FeedbackForm from "../components/FeedbackForm"
      ---
      <FeedbackForm client:load />
      ```
      </Fragment>
      <Fragment slot="react">
      ```astro title="src/pages/index.astro" "client:load"
      ---
      import FeedbackForm from "../components/FeedbackForm"
      ---
      <FeedbackForm client:load />
      ```
      </Fragment>
      <Fragment slot="solid">
      ```astro title="src/pages/index.astro" "client:load"
      ---
      import FeedbackForm from "../components/FeedbackForm"
      ---
      <FeedbackForm client:load />
      ```
      </Fragment>
      <Fragment slot="svelte">
      ```astro title="src/pages/index.astro" "client:load"
      ---
      import FeedbackForm from "../components/FeedbackForm.svelte"
      ---
      <FeedbackForm client:load />
      ```
      </Fragment>
      <Fragment slot="vue">
      ```astro title="src/pages/index.astro" "client:load"
      ---
      import FeedbackForm from "../components/FeedbackForm.vue"
      ---
      <FeedbackForm client:load />
      ```
      </Fragment>
  </UIFrameworkTabs>

{/* ## Extension: Use Zod to validate your form

[Zod form data](https://www.npmjs.com/package/zod-form-data) builds on top of [Zod](https://github.com/colinhacks/zod) to validate your form using a schema. This simplifies your code, as it allows you to declare the fields and their requirements, and let Zod handle the validation.

1. Install `zod` and `zod-form-data`.

    <PackageManagerTabs>
      <Fragment slot="npm">
        ```shell
          npm i zod zod-form-data
        ```
      </Fragment>
      <Fragment slot="pnpm">
        ```shell
          pnpm i zod zod-form-data
        ```
      </Fragment>
      <Fragment slot="yarn">
        ```shell
          yarn add zod zod-form-data
        ```
      </Fragment>
    </PackageManagerTabs>

2. In your API Route file, declare your schema using `zfd.formData` and export it. */}
